package biz

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
	"github.com/go-kratos/kratos/v2/log"
	redis3 "github.com/go-redis/redis"
	"github.com/golang-jwt/jwt/v5"
	"io"
	"net"
	"net/http"
	v1 "gitlab.cqcb.com/shangyou_mic/dscm-approve-api-pb/api/roses/v1"
	"rosesbff/internal/conf"
	"rosesbff/internal/data/redis"
	"strconv"
	"strings"
	"time"

	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
	sts20150401 "github.com/alibabacloud-go/sts-20150401/v2/client"
	util "github.com/alibabacloud-go/tea-utils/v2/service"
	"github.com/alibabacloud-go/tea/tea"
	utils "rosesbff/pkg/util"
)

type Basic struct {
	cfg             *conf.Bootstrap
	redis           *redis.Redis
	aliYunOssClient *sts20150401.Client
	log             log.Logger
	account         AccountRepo

	loginSecretKey []byte
	// 存储用户权限
	storeUserPermit func(userId int64, lists []*v1.UserPermitItem, expiredAt int64)

	ssoUrl       string
	ssoAppId     string
	ssoAppSecret string
}

func NewBasic(
	cfg *conf.Bootstrap,
	redis *redis.Redis,
	log log.Logger,
	account AccountRepo,
) (*Basic, error) {
	result := &Basic{
		cfg:          cfg,
		redis:        redis,
		log:          log,
		account:      account,
		ssoUrl:       "https://testapi-bc.cqcmsd.com/dscm-sso",
		ssoAppId:     "5",
		ssoAppSecret: "mdzXVoCq8vQMy1so56aGmBPXeRtzf9dr",
	}
	{
		config := &openapi.Config{
			// 必填，您的 AccessKey ID
			// AccessKeyId: tea.String("LTAI5tNmetBJcv4VCrJFLxB9"),
			// 必填，您的 AccessKey Secret
			// AccessKeySecret: tea.String("d1IZoTp54uZ0OHOxXivMzLH8nfaMno"),
			AccessKeyId:     &cfg.AliYunOss.AccessKeyId,
			AccessKeySecret: &cfg.AliYunOss.AccessKeySecret,
		}
		// Endpoint 请参考 https://api.aliyun.com/product/Sts
		// config.Endpoint = tea.String("sts.cn-chengdu.aliyuncs.com")
		config.Endpoint = &cfg.AliYunOss.Endpoint
		cli, err := sts20150401.NewClient(config)
		if err != nil {
			return nil, fmt.Errorf("阿里云OSS-客户端初始化失败: %s", err.Error())
		}
		result.aliYunOssClient = cli
	}
	return result, nil
}

type BuildToken struct {
	Token      string
	UserPermit *v1.UserPermit
	Claims     *CustomClaims
}

func (s *Basic) BuildToken(ctx context.Context, user *v1.FxUser) (*BuildToken, error) {
	claims := &CustomClaims{
		UserId: user.Id, // 用户ID
		RegisteredClaims: jwt.RegisteredClaims{
			IssuedAt:  jwt.NewNumericDate(time.Now()),                     // 发行时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 12)), // 过期时间(12小时后)
		},
		OriginUserId: user.UserId,
	}
	if user.Username != nil {
		claims.Username = *user.Username
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenValue, err := token.SignedString(s.loginSecretKey)
	if err != nil {
		return nil, errors.New("save build failed")
	}

	err = s.redis.SetLoginToken(ctx, tokenValue, user.Id, claims.RegisteredClaims.ExpiresAt.Unix())
	if err != nil {
		return nil, errors.New("save build failed")
	}

	// 获取用户权限
	userPermit, err := s.account.ServerGetUserPermitLists(ctx, user)
	if err != nil {
		return nil, errors.New("get user's permits failed")
	}

	if s.storeUserPermit != nil {
		// 缓存用户的权限
		s.storeUserPermit(user.Id, userPermit.Lists, claims.RegisteredClaims.ExpiresAt.Unix())
	}

	return &BuildToken{
		Token:      tokenValue,
		UserPermit: userPermit,
		Claims:     claims,
	}, nil
}

// GetUserId 从上下文中获取用户的id
func (s *Basic) GetUserId(ctx context.Context) int64 {
	value := ctx.Value("UserId")
	if value == nil {
		return 0
	}
	userId, ok := value.(int64)
	if !ok {
		return 0
	}
	return userId
}

// GetUserCategory 从上下文中获取用户的category
func (s *Basic) GetUserCategory(ctx context.Context) int32 {
	value := ctx.Value("Category")
	if value == nil {
		return 0
	}
	category, ok := value.(int32)
	if !ok {
		return 0
	}
	return category
}

// SendMobileVerifyCode 发送短信验证码
func (s *Basic) SendMobileVerifyCode(ctx context.Context, mobile string, code string) error {
	region := s.cfg.AliYunSms.Region
	accessKeyId := s.cfg.AliYunSms.AccessKeyId
	accessKeySecret := s.cfg.AliYunSms.AccessKeySecret
	signName := s.cfg.AliYunSms.SignName
	templateCode := s.cfg.AliYunSms.TemplateCode
	templateParam := s.cfg.AliYunSms.TemplateParam
	templateParam = strings.TrimSpace(templateParam)
	templateParam = strings.TrimPrefix(templateParam, "'")
	templateParam = strings.TrimSuffix(templateParam, "'")
	templateParam = fmt.Sprintf(templateParam, code)

	client, err := dysmsapi.NewClientWithAccessKey(region, accessKeyId, accessKeySecret)
	if err != nil {
		return err
	}

	request := dysmsapi.CreateSendSmsRequest()
	request.Scheme = "https"      // 使用HTTPS协议
	request.PhoneNumbers = mobile // 接收短信的手机号,多个用逗号分隔
	request.SignName = signName
	request.TemplateCode = templateCode
	request.TemplateParam = templateParam

	resp, err := client.SendSms(request)
	if err != nil {
		return err
	}
	if resp.Code != "OK" {
		return fmt.Errorf("短信发送失败, 错误代码:%s, 错误信息:%s", resp.Code, resp.Message)
	}

	return nil
}

// MobileCode 发送手机验证码
func (s *Basic) MobileCode(ctx context.Context, req *v1.MobileCodeRequest) (*v1.MobileCodeReply, error) {
	reply := &v1.MobileCodeReply{}
	req.Scene = strings.TrimSpace(req.Scene)
	key := fmt.Sprintf("%s:%s", req.Scene, req.Mobile)
	verifyCode := utils.RandomString(6)
	if err := s.SendMobileVerifyCode(ctx, req.Mobile, verifyCode); err != nil {
		reply.StatusCode = 500
		reply.Message = "验证码发送失败"
		s.log.Log(log.LevelWarn, "error", err.Error())
		return reply, nil
	}
	if err := s.redis.Client().WithContext(ctx).Set(key, verifyCode, time.Second*300).Err(); err != nil {
		return nil, err
	}
	reply.StatusCode = 200
	reply.Message = "success"
	return reply, nil
}

// VerifyMobileCode 校验手机验证码
func (s *Basic) VerifyMobileCode(ctx context.Context, scene string, mobile string, verifyCode string) (error, error) {
	scene = strings.TrimSpace(scene)
	key := fmt.Sprintf("%s:%s", scene, mobile)
	cmd := s.redis.Client().WithContext(ctx).Get(key)
	if err := cmd.Err(); err != nil {
		if errors.Is(redis3.Nil, err) {
			return errors.New("验证码过期"), nil
		}
		return nil, err
	}
	if cmd.Val() != verifyCode {
		return errors.New("验证码不正确"), nil
	}
	return nil, nil
}

// aliYunCredentialsSecurityToken 获取阿里云OSS临时访问凭证
func (s *Basic) aliYunCredentialsSecurityToken(seconds int64) (string, error) {
	assumeRoleRequest := &sts20150401.AssumeRoleRequest{
		DurationSeconds: tea.Int64(seconds),
		// RoleArn:         tea.String("acs:ram::1406711082313585:role/upload"),
		// RoleSessionName: tea.String("upload"),
		RoleArn:         &s.cfg.AliYunOss.RoleArn,
		RoleSessionName: &s.cfg.AliYunOss.RoleSessionName,
	}
	resp, err := s.aliYunOssClient.AssumeRoleWithOptions(assumeRoleRequest, &util.RuntimeOptions{})
	if err != nil {
		return "", err
	}
	if resp.StatusCode == nil || *resp.StatusCode != 200 {
		return "", errors.New(resp.String())
	}
	if resp.Body == nil || resp.Body.Credentials == nil || resp.Body.Credentials.SecurityToken == nil {
		return "", errors.New("alibaba cloud OSS responded with an unexpected data structure")
	}
	return *resp.Body.Credentials.SecurityToken, nil
}

func (s *Basic) buildSsoUrl(uri string) string {
	return fmt.Sprintf("%s%s", s.ssoUrl, uri)
}

func (s *Basic) getMyIp() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "127.0.0.1"
	}
	for _, address := range addrs {
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}
	return "127.0.0.1"
}

type SsoGetUserInfoReq struct {
	TemporaryCode string `json:"temporaryCode"` // 临时code
}
type SsoGetUserInfoResDataUserinfo struct {
	Id       int64  `json:"id"`
	Name     string `json:"name"`
	RealName string `json:"realName"`
	Email    string `json:"email"`
	Image    string `json:"image"`
	Phone    string `json:"phone"`
}
type SsoGetUserInfoResData struct {
	PartTicket         string                        `json:"partTicket"`
	PartValidTime      int                           `json:"partValidTime"`
	UserId             int64                         `json:"userId"`
	Userinfo           SsoGetUserInfoResDataUserinfo `json:"userinfo"`
	TemporaryCode      string                        `json:"temporaryCode"` // 临时code
	TemporaryValidTime int                           `json:"temporaryValidTime"`
	Timestamp          int64                         `json:"timestamp"`
}
type SsoGetUserInfoRes struct {
	StatusCode int                   `json:"statusCode"`
	Message    string                `json:"message"`
	Data       SsoGetUserInfoResData `json:"data"`
}

// saveSsoGetUserInfoResData 保存用户的数据
func saveSsoGetUserInfoResData(data *SsoGetUserInfoResData, redis *redis.Redis) {
	if data.Timestamp == 0 {
		data.Timestamp = time.Now().Unix()
	}
	key := fmt.Sprintf("%d", data.UserId)
	bts, _ := json.Marshal(data)
	redis.Client().HSet("verifier:part_ticket", key, string(bts))
}

func (s *Basic) ssoGetUserInfo(ctx context.Context, req *SsoGetUserInfoReq) (*SsoGetUserInfoRes, error) {
	send := map[string]any{
		"temporaryCode": req.TemporaryCode,
		"appId":         s.ssoAppId,
		"appSecret":     s.ssoAppSecret,
		"ip":            s.getMyIp(),
	}
	sendBytes, _ := json.Marshal(send)
	resp, err := http.Post(s.buildSsoUrl("/v1/sso/temporaryCode"), "application/json;charset=UTF-8", bytes.NewBuffer(sendBytes))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, errors.New(resp.Status)
	}
	result := bytes.NewBuffer(nil)
	_, err = io.Copy(result, resp.Body)
	if err != nil {
		return nil, err
	}
	res := &SsoGetUserInfoRes{}
	err = json.Unmarshal(result.Bytes(), res)
	if err != nil {
		return nil, err
	}
	saveSsoGetUserInfoResData(&res.Data, s.redis)
	return res, nil
}

// SsoCheckTicket TODO 定时调用当前方法以达到注销用户登录的目录
func (s *Basic) SsoCheckTicket() {
	rc := s.redis.Client()
	tickets := rc.HGetAll("verifier:part_ticket").Val()
	for id, value := range tickets {
		uid, err := strconv.ParseInt(id, 10, 64)
		if err != nil {
			continue
		}
		tmp := &SsoGetUserInfoResData{}
		err = json.Unmarshal([]byte(value), tmp)
		if err != nil {
			continue
		}
		active, err := s.ssoCheckTicket(uid, tmp)
		if err != nil {
			continue
		}
		// 刷新票据
		if active && time.Now().Unix() < tmp.Timestamp+int64(tmp.PartValidTime)-900 {
			_ = s.ssoPartTicket(uid, tmp)
		}
	}
}

type ssoCheckTicket struct {
	StatusCode int    `json:"statusCode"`
	Message    string `json:"message"`
	Data       bool   `json:"data"`
}

func (s *Basic) ssoCheckTicket(userId int64, data *SsoGetUserInfoResData) (active bool, err error) {
	sendBytes, _ := json.Marshal(map[string]any{
		"ticket": data.PartTicket,
		"appId":  s.ssoAppId,
		"userId": userId,
	})
	resp, err := http.Post(s.buildSsoUrl("/v1/sso/checkTicket"), "application/json;charset=UTF-8", bytes.NewBuffer(sendBytes))
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return false, errors.New(resp.Status)
	}
	result := bytes.NewBuffer(nil)
	_, err = io.Copy(result, resp.Body)
	if err != nil {
		return false, err
	}
	res := &ssoCheckTicket{}
	err = json.Unmarshal(result.Bytes(), res)
	if err != nil {
		return false, err
	}
	if !res.Data {
		// 注销用户登录,删除用户信息
		key := fmt.Sprintf("%d", data.UserId)
		s.redis.Client().HDel("verifier:part_ticket", key)
		return false, nil
	}
	return true, nil
}

// ssoPartTicket 刷新用户的票据
func (s *Basic) ssoPartTicket(userId int64, data *SsoGetUserInfoResData) error {
	sendBytes, _ := json.Marshal(map[string]any{
		"partTicket": data.PartTicket,
		"appId":      s.ssoAppId,
		"userId":     userId,
		"ip":         s.getMyIp(),
	})
	resp, err := http.Post(s.buildSsoUrl("/v1/sso/partTicket"), "application/json;charset=UTF-8", bytes.NewBuffer(sendBytes))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}
	result := bytes.NewBuffer(nil)
	_, err = io.Copy(result, resp.Body)
	if err != nil {
		return err
	}
	res := &SsoGetUserInfoRes{}
	err = json.Unmarshal(result.Bytes(), res)
	if err != nil {
		return err
	}
	saveSsoGetUserInfoResData(&res.Data, s.redis)
	return nil
}
